package blackeagle.ems.dal;

import blackeagle.ems.BlackEagleEMS;
import blackeagle.ems.Logger;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import se.oru.informatik.InformatikDB;
import se.oru.informatik.InformatikException;
import se.oru.informatik.StringHelper;

/**
 * An abstract class acting as a list of DataRecord<T>.
 */
public abstract class DataList<T extends DataRecord<T>, L extends DataList<T,L>> implements Collection<T> {
    private Integer first = 0, skip = 0;
    private ArrayList<T> collection = new ArrayList<T>();
    private ArrayList<Where> wheres = new ArrayList<Where>();

    /**
     * Gets the amount of records to fetch from the database.
     * @return The amount of records to fetch from the database. Default is 0.
     */
    public Integer getFirst(){
        return this.first;
    }
    
    /**
     * Sets the amount of records to fetch from the database.
     * @param value A positive integer.
     */
    public void setFirst(Integer value){
        if(value<0)value=0;
        
        this.first=value;
    }
    
    /**
     * Gets the amount of records to skip in the next query.
     * @return The amount of records to skip. Default is 0.
     */
    public Integer getSkip(){
        return this.skip;
    }
    
    /**
     * Sets the amount of records to skip in the next query.
     * @param value A positive integer.
     */
    public void setSkip(Integer value){
        if(value<0)value=0;
        
        this.skip=value;
    }
    
    /**
     * Executes the delete-command for every record contained in this collection. If any record fails deletion, execution is stopped.
     * @return <code>true</code> if all records were successfully deleted; otherwise <code>false</code>
     */
    public Boolean delete(){
        for(T entity : this.collection){
            if(!entity.delete()){
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * Fetches a record from the collection based on an index.
     * @param index The index to attempt to fetch a record on.
     * @return <code>null</code> if index is out of range; otherwise the record.
     */
    public T get(Integer index){
        T retval = null;
        
        if(index>=0&&index<this.collection.size()){
            retval = this.collection.get(index);
        }
        
        return retval;        
    }
    
    /**
     * Fetch a record programmatically based on its id.
     * @param id The id to fetch on.
     * @return A record.
     */
    public abstract T getById(Integer id);
    
    /**
     * Gets the type of the record class.
     * @return The type of the DataRecord class.
     */
    public Class<T> getRecordClass(){
        ParameterizedType type = (ParameterizedType)this.getClass().getGenericSuperclass();
        Class<T> t = (Class<T>)type.getActualTypeArguments()[0];
        
        return t;
    }
    
    /**
     * Attempts to load records from the database based on the specified parameters.
     * @return All the records returned by a SELECT-query based on the specified parameters.
     * 
     * @see blackeagle.ems.dal.DataList#getFirst()
     * @see blackeagle.ems.dal.DataList#setFirst(java.lang.Integer) 
     * 
     * @see blackeagle.ems.dal.DataList#getSkip()
     * @see blackeagle.ems.dal.DataList#setSkip(java.lang.Integer) 
     * 
     * @see blackeagle.ems.dal.Where
     */
    public L load(){
        Class<T> t = this.getRecordClass();
        Annotation a = t.getAnnotation(DataClass.class);
        if(a==null)return(L)this;
        DataClass dc = (DataClass)a;
        if(dc.dataName().equals(""))return(L)this;
        
        this.clear();
        
        StringBuilder sb = new StringBuilder();
        sb.append("SELECT");
        
        if(this.first>0){
            sb.append(" FIRST ");
            sb.append(this.first);
        }
        
        sb.append(" SKIP ");
        sb.append(this.skip);
        
        sb.append(" *");
        
        sb.append(" FROM ");
        sb.append(dc.dataName());
        
        if(this.wheres.size()>0){
            sb.append(" WHERE");
            
            for(int i=0;i<this.wheres.size();i++){
                Where w = this.wheres.get(i);
                if(i!=0){
                    sb.append(" AND");
                }
                
                sb.append(" ");
                sb.append(w.toString());
            }
            
            this.wheres.clear();
        }
        
        String query = sb.toString();
        
        InformatikDB iDB = null;
        try{
            iDB = new InformatikDB(BlackEagleEMS.connectionString);
        }catch(InformatikException e){
            return(L)this;
        }
        
        ArrayList<HashMap<String,String>> result;
        
        try{
            result = iDB.fetchRows(query);
        }catch(InformatikException e){
            return (L)this;
        }
        
        this.loadData(result);
        
        return (L)this;
    }
    
    /**
     * Attempts to create DataRecords and fill them with data from a List of String-String Maps.
     * @param data The HashMap<String,String>-list containing the data.
     */
    public void loadData(ArrayList<HashMap<String,String>> data){
        if(data==null)return;
        
        Class<T> t = this.getRecordClass();
        
        for(Map<String,String> row : data){
            T entity = null;
            
            try{
                entity=t.newInstance();
            }catch(Exception e){
                Logger.log(StringHelper.sprintf("Unable to create new instance of type '#'",t.getSimpleName()));
                
                continue;
            }
            
            entity.fillData(row);
            this.collection.add(entity);
        }
    }
    
    /**
     * Executes the save-function of all the records in this collection
     */
    public void save(){
        for(T t : this.collection){
            t.save();
        }
    }
    
    /**
     * Adds a where-statement
     * @param where A Where-statement to be used in the next SELECT-query.
     * @return This DataList-object with the Where-object added.
     */
    public L where(Where where){
        this.wheres.add(where);
        return (L)this;
    }
    
    @Override
    public boolean add(T e) {
        if(e==null)return false;
        
        /*
         * TODO: ? Check for duplicate UNIQUEs or PKs ?
         */
        
        return this.collection.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends T> c) {
        for(T e : c){
            if(!this.add(e)){
                return false;
            }
        }
        
        return true;
    }

    @Override
    public void clear() {
        this.collection.clear();
    }

    @Override
    public boolean contains(Object o) {
        return this.collection.contains(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return this.collection.containsAll(c);
    }

    @Override
    public boolean isEmpty() {
        return this.collection.isEmpty();
    }

    @Override
    public Iterator<T> iterator() {
        return this.collection.iterator();
    }

    @Override
    public boolean remove(Object o) {
        return this.collection.remove(o);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return this.collection.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return this.collection.retainAll(c);
    }

    @Override
    public int size() {
        return this.collection.size();
    }

    @Override
    public Object[] toArray() {
        return this.collection.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return this.collection.toArray(a);
    }
}
